package de.koller.aframe.util.noise.n2D;

import de.koller.aframe.util.math.Function2DPreview;
import de.koller.aframe.util.math.IFunction2D;
import de.koller.aframe.util.math.interpolation.CubicInterpolation;
import de.koller.oceanFramework.math.RVec3;
import java.util.Random;

public class Noise2D implements IFunction2D {

	public final float[][] noise;

	public Noise2D( Random rnd, int size ) {
		if( size < 4 )
			throw new IllegalArgumentException( "must have at least size 4" );
		this.noise = new float[size][size];
		
		for( int x = 0; x < noise.length; x++ )
			for( int y = 0; y < noise[0].length; y++ )
				noise[x][y] = rnd.nextFloat();
	}
	
	public Noise2D( float[][] noise ) {
		this.noise = noise;
	}
	
	@Override
	public float getValue( float x, float y ) {
		if( x >= 1 ) x = 0.9999f;
		if( y >= 1 ) y = 0.9999f;
		if( x < 0 ) x = 0;
		if( y < 0 ) y = 0;
		
		int a = noise.length - 3;
		x *= a;
		y *= a;
		
		float zx = x % 1f;
		float zy = y % 1f;
		
		int dx = (int)Math.floor( x );
		int dy = (int)Math.floor( y );
		
		return CubicInterpolation.bicubic( zx, zy, 
				noise[0+dx][0+dy], noise[1+dx][0+dy], noise[2+dx][0+dy], noise[3+dx][0+dy],
				noise[0+dx][1+dy], noise[1+dx][1+dy], noise[2+dx][1+dy], noise[3+dx][1+dy],
				noise[0+dx][2+dy], noise[1+dx][2+dy], noise[2+dx][2+dy], noise[3+dx][2+dy],
				noise[0+dx][3+dy], noise[1+dx][3+dy], noise[2+dx][3+dy], noise[3+dx][3+dy] );
	}
	
	public RVec3 getDerivate( float x, float y ) {
		float dh = 1f / noise.length;
		float fdx = CubicInterpolation.cubicDerivate( 0, 
				getValue( x - dh, y ),
				getValue( x - 0, y ),
				getValue( x + dh, y ),
				getValue( x + dh*2, y ) );
		
		//System.out.println( getValue( x + dh, y ) + " " + getValue( x, y ));
		
		float fdy = CubicInterpolation.cubicDerivate( 0, 
				getValue( x, y - dh ),
				getValue( x, y - 0 ),
				getValue( x, y + dh ),
				getValue( x, y + dh*2 ) );
		
		//System.out.println( fdx + " " + fdy );
		RVec3 vx = new RVec3( 1f / noise.length, 0, fdx ).normalize();
		RVec3 vy = new RVec3( 0, 1f / noise.length, fdy ).normalize();
		
		RVec3 n = RVec3.cross( vx, vy ).normalize();
		if( n.z < 0 ) n.negate();
		return n;
	}
	
//	public RVec3 getDerivate( float x, float y, float derivateRadius ) {
//		float f = this.getValue( x, y );
//		
//		RVec3 vhp = new RVec3(  derivateRadius, 0, getValue( x+derivateRadius, y ) - f ).normalize();
//		RVec3 vhn = new RVec3( -derivateRadius, 0, f - getValue( x-derivateRadius, y ) ).normalize();
//		RVec3 vvp = new RVec3( 0,  derivateRadius, getValue( x, y+derivateRadius ) - f ).normalize();
//		RVec3 vvv = new RVec3( 0, -derivateRadius, f - getValue( x, y-derivateRadius ) ).normalize();
//		
//		RVec3 np = RVec3.cross( vhp, vvp ).normalize();
//		RVec3 nn = RVec3.cross( vhn, vvv ).normalize();
//		
//		RVec3 n = new RVec3().add( np ).add( nn ).normalize();
//		if( n.z < 0f )
//			n.negate();
//		
//		return n;
//	}
	
	public void normalize( float height ) {
		float maxV = Float.MIN_VALUE;
		float minV = Float.MAX_VALUE;
		
		for( int x = 0; x < noise.length; x++ )
			for( int y = 0; y < noise.length; y++ ) {
				maxV = Math.max( noise[x][y], maxV );
				minV = Math.min( noise[x][y], minV );
			}
		
		for( int x = 0; x < noise.length; x++ )
			for( int y = 0; y < noise.length; y++ )
				noise[x][y] = (noise[x][y] - minV) * (height / (maxV-minV));
	}
	
	public void normalizeMaxBound( float maxHeight ) {
		for( int x = 0; x < noise.length; x++ )
			for( int y = 0; y < noise.length; y++ )
				noise[x][y] *= (1f / maxHeight);
	}
	
	public static void main( String[] args ) {
		Random r = new Random();
		while( true )
			Function2DPreview.showFunction( 0, 0, 1, 1, new Noise2D( r, 16 ) );
	}
	
}
